import math
import os
import socket
import time
from typing import Union

from apscheduler.schedulers.background import BackgroundScheduler
from django.core.cache import cache
from django.http import HttpResponse, JsonResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from jinja2 import Environment, FileSystemLoader
from pyecharts import options as opts
from pyecharts.charts import Bar, Grid, Pie, Page, Liquid, MapGlobe, Surface3D
from pyecharts.commons.utils import JsCode
from pyecharts.faker import POPULATION
from pyecharts.globals import CurrentConfig, ThemeType

from pms import models
from pms.utils import FakeUtil, RabbitUtil
from yl_pms import utils
from yl_pms.utils import PTN

CurrentConfig.GLOBAL_ENV = Environment(loader=FileSystemLoader("./templates/chart/"))
scheduler = BackgroundScheduler()

log = utils.log()


def check_user():
    """检查是否有登录用户，若没有则结束父进程及当前进程"""
    cache.set('scheduler', 'run', 70)
    pid = cache.get('pid')
    if cache.get('exec-user') is not None:
        utils.extend_user_expire()
    user = cache.get('login-user')
    msg = '{}-{}'.format(pid, user)
    log.info(msg)
    if user is None:
        cmd = '/pyweb/yl_pms/operate.sh kil {}'.format(pid)
        utils.MysqlUtil.set_run_env(pid, 0)
        log.info(cmd)
        cache.delete('scheduler')
        exec_cmd(cmd)
        scheduler.shutdown(wait=False)


def is_stop(port):
    """判断端口服务是已经停止了"""
    try:
        sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ip = '192.168.18.13'
        sc.settimeout(5)
        sc.connect((ip, port))
        return False
    except ConnectionRefusedError:
        return True


def check_port():
    """检查端口服务，保证端口服务状态和数据库中存储的一致"""
    cache.set('scheduler', 'run', 20)
    id_port = utils.MysqlUtil.get_id_port()
    log.info('ip-port:{}'.format(id_port))
    if len(id_port) != 0:
        for port in id_port:
            env = id_port[port]
            if env != 0 and is_stop(port):
                utils.MysqlUtil.set_env_by_port(port)
    else:
        log.info('scheduler stopped')
        cache.delete('scheduler')
        scheduler.shutdown(wait=False)


def start_scheduler(pid):
    """启动定时任务"""
    if pid == 1:
        msg = 'p{} start scheduler'.format(pid)
        log.info(msg)
        scheduler.add_job(check_port, 'interval', seconds=10)
    elif pid is not None:
        msg = 'p{} start scheduler'.format(pid)
        log.info(msg)
        scheduler.add_job(check_user, 'interval', seconds=60)
    scheduler.start()


def get_pid(request):
    """根据请求url获取端口，然后根据端口获取工程代号"""
    port = request.get_host().split(':')[1]
    return utils.MysqlUtil.get_pid_by_port(port)


def get_port(request):
    """根据请求url获取端口"""
    return request.get_host().split(':')[1]


def cache_timeout(request):
    """缓存超时时间，每次请求会更新超时时间，目的是为了判断主工程是否还有用户登录中"""
    sta = cache.get('scheduler')
    pid = get_pid(request)
    env = utils.MysqlUtil.get_run_env(pid)
    cache.set('pid', pid)
    cache.set('env', env)
    cache.set('login-user', request.user.username, 60 * 30)
    if sta is None:
        # 控制程序是否运行
        start_scheduler(pid)


def index(request):
    cache_timeout(request)

    """1、垂直布局-Page-包含两行"""
    page_1 = Page(layout=Page.SimplePageLayout)

    """1.1、水平布局-Grid-第一行"""
    grid1_1 = Grid(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width='1700px', height="800px"))

    """1.2、水平布局-Grid-第一行"""
    grid1_2 = Grid(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width='1700px', height="350px"))
    l1 = Liquid().add(
        "在线率", [0.975], center=["31%", "40%"], label_opts=opts.LabelOpts(font_size=40, position="inside", )
    ).set_global_opts(title_opts=opts.TitleOpts(title="在线率", pos_left="29%"))
    l2 = Liquid().add(
        "告警率", [0.0325], center=["65%", "40%"], label_opts=opts.LabelOpts(
            font_size=40, position="inside",
            formatter=JsCode(
                """function (param) {
                        return (Math.floor(param.value * 10000) / 100) + '%';
                    }"""
            ),
        ),
    ).set_global_opts(title_opts=opts.TitleOpts(title="告警率", pos_left="63%"))

    """1.3、水平布局-Grid-第二行"""
    grid1_3 = Grid(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width='1700px'))
    # 【饼图】
    inner_x_data = ["在线", "离线"]
    inner_y_data = [335, 6]
    inner_data_pair = [list(z) for z in zip(inner_x_data, inner_y_data)]
    outer_x_data = ["塔吊区", "车库顶板区", "UBB结构体系", "风速风向", "室内改造区", "30米悬挑拆除区", "外立面防护"]
    outer_y_data = [300, 15, 10, 6, 4, 2, 4]
    outer_data_pair = [list(z) for z in zip(outer_x_data, outer_y_data)]
    p1 = Pie().add(
        series_name="",
        data_pair=inner_data_pair,
        radius=[0, "30%"],
        center=["30%", "50%"],
    ).add(
        series_name="",
        radius=["40%", "55%"],
        data_pair=outer_data_pair,
        center=["30%", "50%"],
    ).set_global_opts(
        title_opts=opts.TitleOpts(title="测点在线统计", subtitle="各位置测点的在线、离线统计情况", pos_left="26%"),
        legend_opts=opts.LegendOpts(orient="vertical", pos_left="5%")
    ).set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))

    x_data = ["红色预警", "黄色预警", "蓝色预警"]
    y_data = [3, 2, 6]
    data_pair = [list(z) for z in zip(x_data, y_data)]
    p2 = Pie().add(
        "",
        data_pair,
        center=["70%", "50%"],
    ).set_global_opts(
        title_opts=opts.TitleOpts(title="测点预警统计", subtitle="各等级预警统计情况", pos_left="65%"),
        legend_opts=opts.LegendOpts(orient="vertical"),
    ).set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))

    """1.4、水平布局-Grid-第三行"""
    grid1_4 = Grid(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width='1700px'))
    list1 = ["塔吊区", "车库顶板区", "UBB结构体系", "室内改造区", "30米悬挑拆除区", "外立面防护", ]
    list2 = [
        {"value": 12, "percent": 12 / (12 + 3)},
        {"value": 23, "percent": 23 / (23 + 5)},
        {"value": 37, "percent": 37 / (33 + 4)},
        {"value": 52, "percent": 52 / (52 + 2)},
        {"value": 66, "percent": 66 / (66 + 7)},
        {"value": 79, "percent": 79 / (79 + 3)},
    ]
    list3 = [
        {"value": 3, "percent": 3 / (12 + 3)},
        {"value": 5, "percent": 5 / (23 + 5)},
        {"value": 0, "percent": 0 / (33 + 4)},
        {"value": 2, "percent": 2 / (52 + 2)},
        {"value": 7, "percent": 7 / (66 + 7)},
        {"value": 3, "percent": 3 / (79 + 3)},
    ]
    bar = Bar(
        init_opts=opts.InitOpts(
            theme=ThemeType.LIGHT,
            animation_opts=opts.AnimationOpts(animation_delay=1000, animation_easing="elasticOut")
        ),
    ).add_xaxis(list1).add_yaxis(
        "在线", list2, stack="stack1", category_gap="50%"
    ).add_yaxis(
        "离线", list3, stack="stack1", category_gap="50%"
    ).set_series_opts(
        label_opts=opts.LabelOpts(
            position="right",
            formatter=JsCode(
                "function(x){return Number(x.data.percent * 100).toFixed() + '%';}"
            ),
        ),
        itemstyle_opts={
            "normal": {
                "barBorderRadius": [5, 5, 5, 5],
            }
        }
    ).set_global_opts(title_opts=opts.TitleOpts(title="各分区测点在线、离线情况统计", pos_left="10%"))

    # 将图表分别添加到grid对象里面去 对grid的pos参数而言，pos_left是显示在靠右的位置 pos_right同理
    grid1_2.add(l1, grid_opts=opts.GridOpts(pos_right="10%")).add(l2, grid_opts=opts.GridOpts(pos_right="50%"))
    grid1_3.add(p1, grid_opts=opts.GridOpts(pos_left="10%")).add(p2, grid_opts=opts.GridOpts(pos_left="50%"))
    grid1_4.add(bar, grid_opts=opts.GridOpts(pos_left="10%"))
    # page里可以add多种元素，grid chart image等等
    page_1.add(grid1_2, grid1_3, grid1_4)
    return HttpResponse(page_1.render_embed())


def globe(request):
    data = [x for _, x in POPULATION[1:]]
    low, high = min(data), max(data)
    MapGlobe(
        init_opts=opts.InitOpts(theme=ThemeType.ROMA, width='1700px', height="800px")
    ).add_schema().add(
        maptype="world",
        series_name="World Population",
        data_pair=POPULATION[1:],
        is_map_symbol_show=False,
        label_opts=opts.LabelOpts(is_show=False),
    ).set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            min_=low,
            max_=high,
            range_text=["max", "min"],
            is_calculable=True,
            range_color=["lightskyblue", "yellow", "orangered"],
        )
    ).render("./templates/sf/map_globe_base.html")
    return HttpResponse(content=open("./templates/sf/map_globe_base.html").read())


def surface(request):
    def float_range(start: int, end: int, step: Union[int, float], round_number: int = 2):
        """
        浮点数 range
        :param start: 起始值
        :param end: 结束值
        :param step: 步长
        :param round_number: 精度
        :return: 返回一个 list
        """
        temp = []
        while True:
            if start < end:
                temp.append(round(start, round_number))
                start += step
            else:
                break
        return temp

    def surface3d_data():
        for t0 in float_range(-3, 3, 0.05):
            y = t0
            for t1 in float_range(-3, 3, 0.05):
                x = t1
                z = math.sin(x ** 2 + y ** 2) * x / 3.14
                yield [x, y, z]

    Surface3D(
        init_opts=opts.InitOpts(theme=ThemeType.ROMA, width='1700px', height="800px")
    ).add(
        series_name="",
        shading="color",
        data=list(surface3d_data()),
        xaxis3d_opts=opts.Axis3DOpts(type_="value"),
        yaxis3d_opts=opts.Axis3DOpts(type_="value"),
        grid3d_opts=opts.Grid3DOpts(width=100, height=40, depth=100),
    ).set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            dimension=2,
            max_=1,
            min_=-1,
            range_color=[
                "#313695",
                "#4575b4",
                "#74add1",
                "#abd9e9",
                "#e0f3f8",
                "#ffffbf",
                "#fee090",
                "#fdae61",
                "#f46d43",
                "#d73027",
                "#a50026",
            ],
        )
    ).render("./templates/sf/surface_wave.html")
    return HttpResponse(content=open("./templates/sf/surface_wave.html").read())


def fake(request):
    """模拟数据页面"""
    return render(request, 'sf/stream_faker.html')


class TPL:
    """sql模板"""
    TIME = " and time >= '{}' and time <= '{}' "
    TAG = "/usr/wlf/.py-influx.sh timetag {} \"delete from {} where pid = '{}' and sc = '{}' {} \""
    FIELD = "/usr/wlf/.py-influx.sh field {} \"select * from {} where pid = '{}' and sc = '{}' {} \""
    RESTORE = "/usr/wlf/.py-influx.sh restore {} {}"


def __fdt(dt):
    """
    格式化日期格式
    :param dt: yyyy-MM-dd mm:HH:ss
    :return: yyyy-MM-ddTmm:HH:ss+08:00
    """
    return dt.replace(' ', 'T') + '+08:00'


def __ffd(fd):
    """
    组装字段范围条件
    :param fd: conditions
    :return: str_sql
    """
    res = ''
    for c in fd:
        lower = c['lower']
        upper = c['upper']
        aor = c['and_or']
        qid = c['quota_id']
        # 获取是对 v几 进行条件限制
        qc = models.SettingsDataQuota.objects.get(id=qid).code
        # 当只有一个删除条件时
        if len(fd) == 1:
            res += __wrs(qc, lower, upper)
        else:
            # 当有多个删除条件时，判断逻辑运算关系
            fs = __wrs(qc, lower, upper)
            if len(res) != 0 and aor == 1:
                fs = fs.replace(PTN.PA, PTN.PO)
            res += fs
    return res


def __wrs(qc, lower, upper):
    """
    组装sql
    :param qc: 指标序号
    :param lower: 阈值下限
    :param upper: 阈值上限
    :return: 组装好的sql片段
    """
    res = ''
    aot = ' {} {} v{} {} {} {}'
    if lower is not None and upper is not None:
        if lower == upper:
            res += aot.format(PTN.AND, PTN.LP, qc, PTN.EQ, lower, PTN.RP)
        elif lower > upper:
            res += aot.format(PTN.AND, PTN.LP, qc, PTN.GTE, lower, PTN.EPT)
            res += aot.format(PTN.OR, PTN.EPT, qc, PTN.LTE, upper, PTN.RP)
        elif lower < upper:
            res += aot.format(PTN.AND, PTN.LP, qc, PTN.GTE, lower, PTN.EPT)
            res += aot.format(PTN.AND, PTN.EPT, qc, PTN.LTE, upper, PTN.RP)
    elif lower is not None:
        res += aot.format(PTN.AND, PTN.EPT, qc, PTN.GTE, lower, PTN.EPT)
    elif upper is not None:
        res += aot.format(PTN.AND, PTN.EPT, qc, PTN.LTE, upper, PTN.EPT)
    return res


def del_confirm(request):
    rid = request.GET.get("rid")
    sc = request.GET.get("sc")
    st = request.GET.get("st")
    et = request.GET.get("et")
    conditions = models.PyDBCondition.objects.filter(handle_id=rid).values()
    if (st == 'None' or et == 'None') and len(conditions) == 0:
        title = '请求条件有误！'
        subtitle = '如果没有字段范围条件，开始时间【start_time】和结束时间【end_time】都是必须的！'
        return render(request, 'sf/notice.html', {'title': title, 'subtitle': subtitle})
    else:
        # 判断有没有字段范围条件
        if len(conditions) == 0:
            # 如果没有字段范围条件，则必有时间范围条件
            dt_range = TPL.TIME.format(__fdt(st), __fdt(et))
            cmd = TPL.TAG.format('db_gkc_mix', 'raw_yb', 49, sc, dt_range)
        else:
            # 有字段范围条件
            fd_range = __ffd(conditions)
            # 再判断有没有时间范围条件
            if st != 'None' and et != 'None':
                dt_range = TPL.TIME.format(__fdt(st), __fdt(et))
                fd_range += dt_range
            cmd = TPL.FIELD.format('db_gkc_mix', 'raw_yb', 49, sc, fd_range)
        count = utils.InfluxUtil.exec(cmd)
        log.info('del_confirm-rid:{} sc:{} st:{} et:{} count:{}'.format(rid, sc, st, et, count))
        if int(count) == 0:
            title = '未查询到相关数据！'
            subtitle = '请检查下条件吧！'
            return render(request, 'sf/notice.html', {'title': title, 'subtitle': subtitle})
        else:
            cache.set('cmd-del', cmd)
            return render(request, 'sf/confirm_del.html', {'rid': rid, 'count': count})


def res_confirm(request):
    rid = request.GET.get("rid")
    res = models.PyDBHandle.objects.get(id=rid)
    log.info('res_confirm-rid: {} tbl_bk: {} count: {}'.format(rid, res.tbl_bk, res.count))
    if res.count == 0:
        title = '未查询到相关数据！'
        subtitle = '请检查下条件吧！'
        return render(request, 'sf/notice.html', {'title': title, 'subtitle': subtitle})
    else:
        return render(request, 'sf/confirm_res.html', {'rid': rid, 'tbl': res.tbl_bk, 'count': res.count})


def delete(request):
    rid = request.POST.get("rid")
    cmd = cache.get('cmd-del')
    count = request.POST.get("count")
    models.PyDBHandle.objects.filter(id=rid).update(status=1, count=int(count))
    cmd += PTN.SY
    tbl_raw = utils.InfluxUtil.exec(cmd)
    cmd = cmd.replace(PTN.RYB, PTN.MYB)
    tbl_mean = utils.InfluxUtil.exec(cmd)
    tables = tbl_raw + PTN.COMMA + tbl_mean
    models.PyDBHandle.objects.filter(id=rid).update(status=2, tbl_bk=tables)
    return HttpResponseRedirect('/admin/pms/pydbhandle/')


def get_influxdb():
    pid = cache.get('pid')
    return utils.get_influx_env(pid).db


def restore(request):
    rid = request.POST.get("rid")
    tables = request.POST.get("tbl")
    tbl_raw = tables.split(PTN.COMMA)[0]
    tbl_mean = tables.split(PTN.COMMA)[1]
    cmd = TPL.RESTORE.format(tbl_raw, get_influxdb())
    utils.InfluxUtil.exec(cmd)
    cmd = TPL.RESTORE.format(tbl_mean, get_influxdb())
    utils.InfluxUtil.exec(cmd)
    models.PyDBHandle.objects.filter(id=int(rid)).update(status=3, tbl_bk=None, count=0)
    return HttpResponseRedirect('/admin/pms/pydbhandle/')


@csrf_exempt
def set_env(request):
    """设置数据模拟执行环境"""
    env = request.POST.get("env")
    env_id = utils.STA.env(env.lower())
    models.FakeEnv.objects.filter(is_enable=1).update(is_enable=0)
    models.FakeEnv.objects.filter(env=env_id).update(is_enable=1)
    RabbitUtil.init(models.FakeEnv.objects.get(is_enable=1))
    cache.set('conf-user', request.user.username)
    cache.set('conf-env', env_id)
    return JsonResponse({"result": 0, "msg": "执行成功"})


@csrf_exempt
def get_env(request):
    """获取数据模拟执行环境"""
    obj = models.FakeEnv.objects.filter(is_enable=1).values()
    env = int(cache.get('env'))
    if len(obj) == 0 or obj[0]['env'] != env:
        models.FakeEnv.objects.update(is_enable=0)
        models.FakeEnv.objects.filter(env=env).update(is_enable=1)
    env = models.FakeEnv.objects.get(is_enable=1)
    RabbitUtil.init(env)
    cache.set('conf-user', request.user.username)
    cache.set('conf-env', env.env)
    return JsonResponse({"env": env.env})


def projects(request):
    """总工程页面
    注.每个工程内包含多个项目。
    """
    # return render(request, 'sf/main_bk.html')
    return render(request, 'sf/main_projects.html')


def whirl(request):
    return render(request, 'sf/particle_whirl.html')


def exec_cmd(cmd):
    os.system(cmd)


@csrf_exempt
def setup(request):
    pid = request.POST.get('pid')
    env = request.POST.get('env')
    force = request.POST.get('force')
    user = cache.get('login-user')
    if utils.MysqlUtil.exist_env(pid, env):
        port = utils.MysqlUtil.get_port(pid)
        run_env = utils.MysqlUtil.get_run_env(pid)
        if pid == '1' or run_env == int(env):
            res = port
        elif run_env == 0:
            utils.mod_settings_env(pid, env)
            utils.MysqlUtil.set_run_env(pid, env)
            cmd = '/pyweb/yl_pms/operate.sh run {}'.format(pid)
            log.info(cmd)
            exec_cmd(cmd)
            res = port
        elif force == '1':
            utils.mod_settings_env(pid, env)
            utils.MysqlUtil.set_run_env(pid, env)
            res = port
            time.sleep(1)
        else:
            res = utils.STA.get_env(run_env)
    else:
        res = 'false'
    return JsonResponse({"port": res, "user": user})


@csrf_exempt
def env_config(request):
    siz = 0
    exec_user = 'nu'
    FakeUtil.init()
    conf_list = FakeUtil.get_conf_list()
    if len(conf_list) != 0:
        siz = 1
    cache_user = cache.get('exec-user')
    if cache_user:
        exec_user = cache_user
    return JsonResponse({"size": siz, "exec-user": exec_user})


@csrf_exempt
def main(request):
    search = request.GET.get('search')
    print('search:{}'.format(search))
    res = {}
    pns = []
    if search is not None:
        ps = models.MainProject.objects.filter(pn__icontains=search).values()
    else:
        ps = models.MainProject.objects.values()
    for i in range(len(ps)):
        p = ps[i]
        pns.append({"id": p['id'], "text": p['pn']})
    res["sta"] = 200
    res["ps"] = pns
    print('main-res:{}'.format(res))
    return JsonResponse(res)
